﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace LiJ.Orm.Core
{
    public class SqlExpressionVisitor
    {
        /// <summary>
        /// 表达中的常量参数列表
        /// </summary>
        public Dictionary<string, object> Parameters { get; }

        /// <summary>
        /// 表达式中的属性参数列表
        /// </summary>
        public Dictionary<string, PropertyInfo> Properties { get; }

        public SqlExpressionVisitor()
        {
            Parameters = new Dictionary<string, object>();
            Properties = new Dictionary<string, PropertyInfo>();
        }

        /// <summary>
        /// 递归解析方法入口
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="sqlVistorFlag"></param>
        public virtual void Visit(Expression expression, SqlVistorFlag sqlVistorFlag)
        {
            switch (expression.NodeType)
            {
                case ExpressionType.Lambda:
                    VisitLambda(expression as LambdaExpression, sqlVistorFlag);
                    break;
                case ExpressionType.MemberAccess:
                    VisitMember(expression as MemberExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.Coalesce:
                case ExpressionType.ArrayIndex:
                case ExpressionType.RightShift:
                case ExpressionType.LeftShift:
                case ExpressionType.ExclusiveOr:
                    VisitBinary(expression as BinaryExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Negate:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.ArrayLength:
                case ExpressionType.Quote:
                case ExpressionType.TypeAs:
                    VisitUnary(expression as UnaryExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Constant:
                    VisitConstant(expression as ConstantExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Parameter:
                    VisitParameter(expression as ParameterExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Call:
                    VisitMethodCall(expression as MethodCallExpression, sqlVistorFlag);
                    break;
                case ExpressionType.New:
                    VisitNew(expression as NewExpression, sqlVistorFlag);
                    break;
                case ExpressionType.NewArrayInit:
                case ExpressionType.NewArrayBounds:
                    VisitNewArray(expression as NewArrayExpression, sqlVistorFlag);
                    break;
                case ExpressionType.MemberInit:
                    VisitMemberInit(expression as MemberInitExpression, sqlVistorFlag);
                    break;
                case ExpressionType.Conditional:
                    VisitConditional(expression as ConditionalExpression);
                    break;
            }
        }

        /// <summary>
        /// 递归解析时，保留上层的IsRight状态
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="sqlVistorFlag"></param>
        protected virtual void VisitRight(Expression expression, SqlVistorFlag sqlVistorFlag)
        {
            var isRight = sqlVistorFlag.IsRight;
            sqlVistorFlag.IsRight = true;
            Visit(expression, sqlVistorFlag);
            sqlVistorFlag.IsRight = isRight;
        }

        #region 不同表达式解析方法

        #region VisitMethodCall

        protected virtual void VisitMethodCall(MethodCallExpression methodCallExpression, SqlVistorFlag sqlVistorFlag)
        {
            var methodName = methodCallExpression.Method.Name;
            switch (methodName)  //可以改成反射调用
            {
                case "Contains":
                    MethodCallLike(methodCallExpression, sqlVistorFlag);
                    break;
            }
        }

        private void MethodCallLike(MethodCallExpression methodCallExpression, SqlVistorFlag sqlVistorFlag)
        {
            Visit(methodCallExpression.Object, sqlVistorFlag);
            sqlVistorFlag.Append(GetUnaryOperater(sqlVistorFlag.UnaryType));
            sqlVistorFlag.Append(" LIKE CONCAT('%',");
            VisitRight(methodCallExpression.Arguments[0], sqlVistorFlag);
            sqlVistorFlag.Append(",'%')");
        }

        #endregion

        protected virtual void VisitBinary(BinaryExpression binaryExpression, SqlVistorFlag sqlVistorFlag)
        {
            var operationAnd = GetBinaryOperater(binaryExpression.NodeType);
            if (binaryExpression.NodeType == ExpressionType.AndAlso ||
                binaryExpression.NodeType == ExpressionType.OrElse)
            {
                sqlVistorFlag.Append("(");
                Visit(binaryExpression.Left, sqlVistorFlag);
                sqlVistorFlag.Append(") ").Append(operationAnd).Append(" (");
                Visit(binaryExpression.Right, sqlVistorFlag);
                sqlVistorFlag.Append(")");
            }
            else
            {
                Visit(binaryExpression.Left, sqlVistorFlag);
                sqlVistorFlag.Append(operationAnd);
                VisitRight(binaryExpression, sqlVistorFlag);
            }
        }

        protected void VisitNewArray(NewArrayExpression newArrayExpression, SqlVistorFlag sqlVistorFlag)
        {
            var original = newArrayExpression.Expressions;
            foreach (var expression in original)
            {
                if (expression.NodeType == ExpressionType.NewArrayInit ||
                    expression.NodeType == ExpressionType.NewArrayBounds)
                {
                    if (expression is NewArrayExpression newExpression)
                        VisitNewArray(newExpression, sqlVistorFlag);
                }
                else
                {
                    Visit(expression, sqlVistorFlag);
                }
            }
        }

        protected virtual void VisitConstant(ConstantExpression constantExpression, SqlVistorFlag sqlVistorFlag)
        {
            var value = constantExpression.Value ?? "null";
            if (sqlVistorFlag.IsRight)
            {
                var parameterName = sqlVistorFlag.GetCustomParameterName();
                sqlVistorFlag.Append(parameterName);

                AddParameter(parameterName, constantExpression.Type == typeof(bool) ? ((bool)value ? 1 : 0) : value);
            }
            else
            {
                sqlVistorFlag.Append(value.ToString());
            }
        }

        protected virtual void VisitUnary(UnaryExpression unaryExpression, SqlVistorFlag sqlVistorFlag)
        {
            var nodeType = sqlVistorFlag.UnaryType;
            sqlVistorFlag.UnaryType = unaryExpression.NodeType;
            Visit(unaryExpression.Operand, sqlVistorFlag);
            sqlVistorFlag.UnaryType = nodeType;
        }

        protected virtual void VisitLambda(LambdaExpression lambdaExpression, SqlVistorFlag sqlVistorFlag)
        {
            Visit(lambdaExpression.Body, sqlVistorFlag);
        }

        protected virtual void VisitNew(NewExpression newExpression, SqlVistorFlag sqlVistorFlag)
        {
            for (int i = 0; i < newExpression.Members.Count; i++)
            {
                var arg = newExpression.Arguments[i];
                var member = newExpression.Members[i];

                sqlVistorFlag.Append(sqlVistorFlag.GetColName(member.Name)).Append("=");
                VisitRight(arg, sqlVistorFlag);
            }
        }

        protected virtual void VisitMember(MemberExpression memberExpression, SqlVistorFlag sqlVistorFlag)
        {
            if (memberExpression.Expression != null &&
                memberExpression.Expression.NodeType == ExpressionType.Parameter)
            {
                if (sqlVistorFlag.IsRight)
                {
                    var propertyParameterName = sqlVistorFlag.GetParameterName(memberExpression.Member.Name);
                    sqlVistorFlag.Append(propertyParameterName);
                    AddMemberProperty(propertyParameterName, memberExpression.Member.DeclaringType.GetProperty(memberExpression.Member.Name));
                }
                else
                {
                    if (memberExpression.Member.DeclaringType.GetTypeInfo().IsGenericType &&
                        memberExpression.Member.DeclaringType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        if (memberExpression.Member.Name == "Value")
                            Visit(memberExpression.Expression, sqlVistorFlag);

                        if (memberExpression.Member.Name != "HasValue") return;

                        var doesNotEqualNull = Expression.MakeBinary(ExpressionType.NotEqual,
                            memberExpression.Expression, Expression.Constant(null));
                        Visit(doesNotEqualNull, sqlVistorFlag);
                    }
                    else sqlVistorFlag.Append(sqlVistorFlag.GetColName(memberExpression.Member.Name));
                }
            } else if (memberExpression.Expression != null && sqlVistorFlag.IsRight)
            {
                var value = Expression.Lambda(memberExpression).Compile().DynamicInvoke();
                Visit(Expression.Constant(value), sqlVistorFlag);
            }
        }

        protected virtual void VisitConditional(ConditionalExpression conditionalExpression)
        {
            //TODO
        }

        protected virtual void VisitMemberInit(MemberInitExpression memberInitExpression, SqlVistorFlag sqlVistorFlag)
        {
            //TODO
        }

        protected virtual void VisitParameter(ParameterExpression parameterExpression, SqlVistorFlag sqlVistorFlag)
        {
            //TODO
        }

        #endregion

        #region 辅助方法

        /// <summary>
        /// 添加成员属性信息
        /// </summary>
        /// <param name="name">属性名称</param>
        /// <param name="propertyInfo">属性信息</param>
        private void AddMemberProperty(string name, PropertyInfo propertyInfo)
        {
            if (!Properties.ContainsKey(name))
            {
                Properties.Add(name, propertyInfo);
            }
        }

        /// <summary>
        /// 添加值对应的参数
        /// </summary>
        /// <param name="parameterName">参数名称</param>
        /// <param name="value">值</param>
        private void AddParameter(string parameterName, object value)
        {
            Parameters.Add(parameterName, value);
        }

        protected virtual string GetBinaryOperater(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.Add:
                    return "+";
                case ExpressionType.And:
                    return "&";
                case ExpressionType.AndAlso:
                    return "AND";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.NotEqual:
                    return "<>";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.Or:
                    return "|";
                case ExpressionType.OrElse:
                    return "OR";
                case ExpressionType.Subtract:
                    return "-";
                case ExpressionType.Multiply:
                    return "*";
                case ExpressionType.Divide:
                    return "/";
                case ExpressionType.Modulo:
                    return "MOD";
                case ExpressionType.Coalesce:
                    return "COALESCE";
                default:
                    return string.Empty;
            }
        }

        protected virtual string GetUnaryOperater(ExpressionType expressionType)
        {
            switch (expressionType)
            {
                case ExpressionType.Not:
                    return "NOT";
                default:
                    return string.Empty;
            }
        }

        #endregion
    }
}